#include <stdio.h>
#include <math.h>
#include <string.h>
#include <float.h>

#include "pub_lib.h"
#include "raylib.h"
#include "ray_trace.h"

Vector3 castRay(Vector3 orig, Vector3 dir, struct Scene* scene, int depth);

// Compute reflection direction
// I - 2 * dotProduct(I, N) * N;
Vector3 reflect(const Vector3 I, const Vector3 N)
{
	Vector3 ret;
	float a = 2.0f * Vector3DotProduct(I, N);
	ret = Vector3Scale(N, a);
	return Vector3Subtract(I, ret);
}

// [comment]
// Compute refraction direction using Snell's law
//
// We need to handle with care the two possible situations:
//
//    - When the ray is inside the object
//
//    - When the ray is outside.
//
// If the ray is outside, you need to make cosi positive cosi = -N.I
//
// If the ray is inside, you need to invert the refractive indices and negate the normal N
// [/comment]
Vector3 refract(const Vector3 I, const Vector3 N, const float ior)
{
	float cosi = clampf(-1, 1, Vector3DotProduct(I, N));
	float etai = 1, etat = ior;
	Vector3 n = N;
	if (cosi < 0) {
		cosi = -cosi;
	} else {
		swapf(etai, etat);
		//n = -N;
		n.x = -N.x;
		n.y = -N.y;
		n.z = -N.z;
	}
	float eta = etai / etat;
	float k = 1 - eta * eta * (1 - cosi * cosi);

	Vector3 ret = {0, 0, 0};
	//return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n;
	if (k >= 0.0f){
		Vector3 a, b;
		float val;
		val = eta * cosi - sqrtf(k);
		a = Vector3Scale(n, val);
		b = Vector3Scale(I, eta);
		ret = Vector3Add(a, b);
	}
	return ret;
}

// [comment]
// Compute Fresnel equation
//
// \param I is the incident view direction
//
// \param N is the normal at the intersection point
//
// \param ior is the material refractive index
// [/comment]
float fresnel(const Vector3 I, const Vector3 N, const float ior)
{
    float cosi = clampf(-1, 1, Vector3DotProduct(I, N));
    float etai = 1, etat = ior;

	float temp;
    if (cosi > 0) {
	    swapf(etai, etat);
	}
    // Compute sini using Snell's law
	temp = 1 - cosi * cosi;
	if (temp < 0.0f)
		temp = 0.0f;
    float sint = etai / etat * sqrtf(temp);
    // Total internal reflection
    if (sint >= 1) {
        return 1;
    } else {
		temp =  1 - sint * sint;
		if (temp < 0.0f)
			temp = 0.0f;

        float cost = sqrtf(temp);
        cosi = fabsf(cosi);
        float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
        float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
        return (Rs * Rs + Rp * Rp) / 2;
    }
    // As a consequence of the conservation of energy, transmittance is given by:
    // kt = 1 - kr;
}

// [comment]
// Returns true if the ray intersects an object, false otherwise.
//
// \param orig is the ray origin
// \param dir is the ray direction
// \param objects is the list of objects the scene contains
// \param[out] tNear contains the distance to the cloesest intersected object.
// \param[out] index stores the index of the intersect triangle if the interesected object is a mesh.
// \param[out] uv stores the u and v barycentric coordinates of the intersected point
// \param[out] *hitObject stores the pointer to the intersected object (used to retrieve material information, etc.)
// \param isShadowRay is it a shadow ray. We can return from the function sooner as soon as we have found a hit.
// [/comment]

bool trace(Vector3 orig, Vector3 dir, struct Scene* p, struct hit_payload *payload)
{
	struct list_head* cur;;
	struct Object* obj;

	float tNearK = FLT_MAX;
	float tNear = FLT_MAX;
    uint32_t indexK;
    Vector2 uvK;

	int hit = false;
	list_for_each(cur, &p->objects) {
		obj = list_entry(cur, struct Object, list);
		tNearK = FLT_MAX;

		if (obj->intersect(obj, orig, dir, &tNearK, &indexK, &uvK) \
				&& (tNearK < tNear))
		{
			payload->hit_obj = obj;
			payload->tNear = tNearK;
			payload->index = indexK;
			payload->uv = uvK;
			tNear = tNearK;
			hit = true;
		}
	}

    return hit;
}

//because accuracy of math calculation, hitPoint may be in object!
//self-intersection when cast this light
//so need re-calculate the origin of light, just add or sub bias
//add or sub bias along normal
//angle: angle of light and Normal
//diffuse: angle must be obtuse, dir.N < 0, add bias
//reflect: angle must be acute,  dir.N > 0, add bias
//refract: if light refract into object:
//	angle must be obtuse, dir.N < 0, sub bias
//	if light refract to outside,:
//	angle must be acute, dir.N  > 0, add bias
//conclusion: only when light is refract, and dir dot N < 0, sub bias
//otherwise, add bias
//
//input parameter sub: set 1 only when light is refract, and dir dot N < 0
//otherwise, set 0
Vector3 adjust_point(Vector3 N, float bias, Vector3 hitPoint, int sub)
{
	Vector3 temp;

	temp = Vector3Scale(N, bias);
	Vector3 hsub = Vector3Subtract(hitPoint, temp);
	Vector3 hadd = Vector3Add(hitPoint, temp);

	if (sub)
		return hsub;
	return hadd;
}

Vector3 cast_reflect(Vector3 dir, Vector3 hitPoint,
	struct hit_payload *payload, struct Scene* scene, int depth)
{
	Vector3 N; //normal
	Vector2 st; //st coordinates
	payload->hit_obj->getSurfaceProperties(payload->hit_obj,
			hitPoint, dir, payload->index, &payload->uv, &N, &st);

	float kr = fresnel(dir, N, payload->hit_obj->ior);
	Vector3 reflectDir = reflect(dir, N);
	Vector3 reflectOrig = adjust_point(N, scene->bias, hitPoint, 0);
	Vector3 hitColor = castRay(reflectOrig, reflectDir, scene, depth + 1);
	hitColor = Vector3Scale(hitColor, kr);
	return hitColor;
}

//REFLECTION_AND_REFRACTION
Vector3 cast_reflect_refract(Vector3 dir, Vector3 hitPoint,
	struct hit_payload *payload, struct Scene* scene, int depth)
{
	Vector3 N; //normal
	Vector2 st; //st coordinates
	payload->hit_obj->getSurfaceProperties(payload->hit_obj,
			hitPoint, dir, payload->index, &payload->uv, &N, &st);

	float ior = payload->hit_obj->ior;
	Vector3 reflectDir = Vector3Normalize(reflect(dir, N));
	Vector3 refractDir = Vector3Normalize(refract(dir, N, ior));
	Vector3 reflectOrig = adjust_point(N, scene->bias, hitPoint, 0);

	int flag = 0; //only when light is refract, and dir dot N < 0, flag set 1
	if(Vector3DotProduct(refractDir, N) < 0.0f)
		flag = 1;
	Vector3 refractOrig = adjust_point(N, scene->bias, hitPoint, flag);

	Vector3 reflectionColor = castRay(reflectOrig, reflectDir, scene, depth + 1);
	Vector3 refractionColor = castRay(refractOrig, refractDir, scene, depth + 1);
	float kr = fresnel(dir, N, ior);
	//reflectionColor * kr + refractionColor * (1 - kr);
	Vector3 a = Vector3Scale(reflectionColor, kr);
	Vector3 b = Vector3Scale(refractionColor, 1 - kr);
	Vector3 hitColor = Vector3Add(a, b);

	return hitColor;
}

// [comment]
// We use the Phong illumation model int the default case. The phong model
// is composed of a diffuse and a specular reflection component.
// [/comment]
Vector3 phong_model(Vector3 dir, Vector3 hitPoint,
	struct hit_payload *payload, struct Scene* scene, int depth)
{
	Vector3 N; //normal
	Vector2 st; //st coordinates
	payload->hit_obj->getSurfaceProperties(payload->hit_obj,
			hitPoint, dir, payload->index, &payload->uv, &N, &st);

	Vector3 lightAmt = {0,0,0}, specularColor = {0,0,0};
	Vector3 shadowPointOrig = adjust_point(N, scene->bias, hitPoint, 0);

	// [comment]
	// Loop over all lights in the scene and sum their contribution up
	// We also apply the lambert cosine law
	// [/comment]
	struct Light* light = scene->lights;
	Vector3 temp;
	for (int i = 0; i < 2; i++, light++) {
		Vector3 lightDir = Vector3Subtract(light->position, hitPoint);
		// square of the distance between hitPoint and the light
		float lightDistance2 = Vector3DotProduct(lightDir, lightDir);
		lightDir = Vector3Normalize(lightDir);
		float LdotN = Vector3DotProduct(lightDir, N);
		if(LdotN < 0.0f)
			LdotN = 0.0f;
		// is the point in shadow, and is the nearest occluding object closer
		// to the object than the light itself?
		struct hit_payload shadow_res;
		bool ret = trace(shadowPointOrig, lightDir, scene, &shadow_res);
		bool inShadow = ret && (shadow_res.tNear * shadow_res.tNear < lightDistance2);

		//lightAmt += inShadow ? 0 : light->intensity * LdotN;
		if (inShadow == false){
			temp = Vector3Scale(light->intensity, LdotN);
			lightAmt = Vector3Add(lightAmt, temp);
		}
		Vector3 negative = {-lightDir.x, -lightDir.y, -lightDir.z};
		Vector3 reflectDir = reflect(negative, N);
		float a;
		a = 0.0f - Vector3DotProduct(reflectDir, dir);
		if (a < 0.0f)
			a = 0.0f;
		a = powf(a, payload->hit_obj->specularExponent);
		temp = Vector3Scale(light->intensity, a);
		specularColor = Vector3Add(specularColor, temp);
	}//end for loop

	Vector3 diffuse = payload->hit_obj->evalDiffuseColor(payload->hit_obj, st);
	temp = Vector3Multiply(lightAmt, diffuse);
	temp = Vector3Scale(temp, payload->hit_obj->Kd);
	Vector3 hitColor = Vector3Scale(specularColor, payload->hit_obj->Ks);
	hitColor = Vector3Add(hitColor, temp);

	return hitColor;
}

// [comment]
// Implementation of the Whitted-style light transport algorithm (E [S*] (D|G) L)
//
// This function is the function that compute the color at the intersection point
// of a ray defined by a position and a direction. Note that thus function is recursive (it calls itself).
//
// If the material of the intersected object is either reflective or reflective and refractive,
// then we compute the reflection/refraction direction and cast two new rays into the scene
// by calling the castRay() function recursively. When the surface is transparent, we mix
// the reflection and refraction color using the result of the fresnel equations (it computes
// the amount of reflection and refraction depending on the surface normal, incident view direction
// and surface refractive index).
//
// If the surface is diffuse/glossy we use the Phong illumation model to compute the color
// at the intersection point.
// [/comment]
Vector3 castRay(Vector3 orig, Vector3 dir, struct Scene* scene, int depth)
{
	Vector3 hitColor = scene->backgroundColor;
    if (depth > scene->maxDepth) {
        return scene->backgroundColor;;
    }

	struct hit_payload payload;
	int hit = trace(orig, dir, scene, &payload);
	if (false == hit)
		return scene->backgroundColor;

	Vector3 hitPoint;//orig + dir * payload->tNear;
	hitPoint = Vector3Scale(dir, payload.tNear);
	hitPoint = Vector3Add(hitPoint, orig);

	switch (payload.hit_obj->materialType){
		case REFLECTION_AND_REFRACTION:
			hitColor = cast_reflect_refract(dir, hitPoint, &payload, scene, depth);
			break;
		case REFLECTION:
			hitColor = cast_reflect(dir, hitPoint, &payload, scene, depth);
			break;
		default:
			hitColor = phong_model(dir, hitPoint, &payload, scene, depth);
			break;
	}//end switch

    return hitColor;
}

/*
 * screen coordinate to [-1,1]
 * [-1, 1] to screen
 * xs = 0.5w(1+x)
 * ys = 0.5h(1+y)
 * */
void invert_viewport(float xs, float ys, float* x, float* y, int w, int h)
{
	*x = (xs * 2.0f / w) - 1.0f;
	*y = 1.0f - (ys * 2.0f / h);
}

// [comment]
// The main render function. This where we iterate over all pixels in the image, generate
// primary rays and cast these rays into the scene. 
// [/comment]
void Render(struct Scene * scene)
{
	float scale = tanf(DEG2RAD * scene->fov * 0.5f);
	float imageAspectRatio = (float)scene->width / scene->height;

	// Use this variable as the eye position to start your rays.
	Vector3 eye_pos = scene->eye_pos;
	int m = 0;
	float x, y;

	for (int j = 0; j < scene->height; ++j)
	{
		for (int i = 0; i < scene->width; ++i)
		{
			// generate primary ray direction
			// TODO: Find the x and y positions of the current pixel to get the direction
            // vector that passes through it.
            // Also, don't forget to multiply both of them with the variable *scale*, and
            // x (horizontal) variable with the *imageAspectRatio*
			invert_viewport(i + 0.5f, j + 0.5f, &x, &y, scene->width, scene->height);
			x = x * scale * imageAspectRatio;
			y = y * scale;
			// the camera looks at -z axis, so z=-1 here
			Vector3 dir = {x, y, -1}; // Don't forget to normalize this direction!
			dir = Vector3Normalize(dir);
			Vector3 color = castRay(eye_pos, dir, scene, 0);
			scene->framebuffer[m++] = toColor(color);
		}
		UpdateProgress(j / (float)scene->height);
	}
}

bool default_intersect(struct Object* obj,
	const Vector3 orig, const Vector3 dir,
	float* tnear, uint32_t* index, Vector2* uv)
{
	return false;
}

bool sphere_intersect(struct Object* obj,
	const Vector3 orig, const Vector3 dir,
	float* tnear, uint32_t* index, Vector2* uv)
{
	struct Sphere *p = (struct Sphere*)obj;

	Vector3 L = Vector3Subtract(orig, p->center);
	float a = Vector3DotProduct(dir, dir);
	float b = 2 * Vector3DotProduct(dir, L);
	float c = Vector3DotProduct(L, L) - p->radius2;
	float t0, t1;
	//t0 is nearest
	if (!solveQuadratic(a, b, c, &t0, &t1))
		return false;
	//ignore negative value
	if (t0 < 0)
		t0 = t1;
	if (t0 < 0)
		return false;
	*tnear = t0;

	return true;
}

bool rayTriangleIntersect(Vector3 v0, Vector3 v1, Vector3 v2,
	Vector3 orig, Vector3 dir, float* tnear, float* u, float* v)
{
	// TODO: Implement this function that tests whether the triangle
	// that's specified bt v0, v1 and v2 intersects with the ray (whose
	// origin is *orig* and direction is *dir*)
	// Also don't forget to update tnear, u and v.
	Vector3 S = Vector3Subtract(orig, v0);
	Vector3 E1 = Vector3Subtract(v1, v0);
	Vector3 E2 = Vector3Subtract(v2, v0);
	Vector3 S1 = Vector3CrossProduct(dir, E2);
	Vector3 S2 = Vector3CrossProduct(S, E1);

	float S1E1 = Vector3DotProduct(S1, E1);
	if (S1E1 == 0.0f)
		return false;

	float invDet = 1 / S1E1;
	float t = Vector3DotProduct(S2, E2) * invDet;
	float b1 = Vector3DotProduct(S1, S) * invDet;
	float b2 = Vector3DotProduct(S2, dir) * invDet;

	if (t>=0 && b1>=0 && b2>=0 && (1-b1-b2)>=0) {
		*tnear = t;
		*u = b1;
		*v = b2;
		return true;
	}

	return false;
}

